package com.landleaf.smarthome.net.hardware;

import android.content.Context;

import com.landleaf.smarthome.device.BaseLocalDeviceModel;
import com.landleaf.smarthome.device.ProductCodeAnnotation;
import com.landleaf.smarthome.model.DeviceAttribute;
import com.landleaf.smarthome.model.DeviceModel;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import dalvik.system.DexFile;

/**
 * When I wrote this, only God and I understood what I was doing
 * Now, God only knows
 * Created by 海航
 * 2020/8/31
 * 功能描述: 协议转换工具类
 */
public class ProtocolUtils {

    private static List<Class> baseClass;


    /**
     * 通过获取485等硬件协议的点位 和点位的值生成一个云端可识别的DeviceAttribute实体类
     *
     * @param deviceModel
     * @param addTag
     * @param addValue
     * @return
     */
    public static DeviceAttribute getAttData4Protocol(DeviceModel deviceModel, int addTag, int addValue, Context context) {
        DeviceAttribute deviceAttribute = new DeviceAttribute();
        String tag = getAttTag4Add(deviceModel, addTag, context);
        String value = getAttValue4Add(deviceModel, tag, addValue, context);
        deviceAttribute.setAttrTag(tag);
        //如果在map中找不到制定的value的类型 那么value点位具体的值 譬如  温湿度  二氧化碳等
        deviceAttribute.setAttrValue(value == null ? String.valueOf(addValue) : value);
        return deviceAttribute;
    }

    /**
     * 通过点位地址 找到AttTag
     *
     * @param deviceModel
     * @param attTagAdd
     * @param
     * @return
     */
    public static String getAttTag4Add(DeviceModel deviceModel, int attTagAdd, Context context) {
        BaseLocalDeviceModel model = getDeviceModel4ProductTag(deviceModel.getProductCode(), context);
        if (model == null) {
            return null;
        }
        Map<String, Integer> add = model.getAttTagAdd();
        Iterator<Map.Entry<String, Integer>> iterator = add.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            if (entry.getValue() == attTagAdd) {
                return entry.getKey();
            }
        }
        return null;
    }

    /**
     * 通过点位地址具体的值 找到AttValue
     *
     * @param deviceModel
     * @param attValueAdd
     * @param
     * @return
     */
    public static String getAttValue4Add(DeviceModel deviceModel, String attTag, int attValueAdd, Context context) {
        BaseLocalDeviceModel model = getDeviceModel4ProductTag(deviceModel.getProductCode(), context);
        Map<String, DeviceAttribute> valueMap = model.getAttValueAdd();
        Iterator<Map.Entry<String, DeviceAttribute>> iterator = valueMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, DeviceAttribute> entry = iterator.next();
            DeviceAttribute value = entry.getValue();
            if (value.getAttrTag().equals(attTag) && value.getAddValue() == attValueAdd) {
                return entry.getKey();
            }
        }
        //如果没找到 那么应该就是 value直接表示具体值的类型 譬如温湿度 等   返回null即可
        return null;
    }

    /**
     * 通过点位地址具体的值 找到AttValue
     *
     * @param deviceModel
     * @param attTagAdd
     * @param attValueAdd 该点位具体的值
     * @return
     */
    public static String getAttValue4Add(DeviceModel deviceModel, int attTagAdd, int attValueAdd, Context context) {
        String attTag = getAttTag4Add(deviceModel, attTagAdd, context);
        return getAttValue4Add(deviceModel, attTag, attValueAdd, context);
    }


    public static int getAdd4AttTag(DeviceModel deviceModel, String attTag, Context context) {
        int attAdd = -1;
        BaseLocalDeviceModel model = getDeviceModel4ProductTag(deviceModel.getProductCode(), context);
        if (model != null) {
            return model.getAttTagAdd().get(attTag);
        }
        return attAdd;
    }

    /**
     * 通过tag找到 value在具体协议地址里面的值
     *
     * @param deviceModel
     * @param attValue
     * @param context
     * @return
     */
    public static int getAdd4AttValue(DeviceModel deviceModel, String attValue, Context context) {
        int attAdd = -1;
        BaseLocalDeviceModel model = getDeviceModel4ProductTag(deviceModel.getProductCode(), context);
        if (model != null) {
            return model.getAttValueAdd().get(attValue).getAddValue();
        }
        return attAdd;
    }


    /**
     * 通过 productTag 获取到当前的 DeviceModel
     *
     * @param productTag
     * @param context
     * @return
     */
    public static BaseLocalDeviceModel getDeviceModel4ProductTag(int productTag, Context context) {
        BaseLocalDeviceModel model = null;
        List<Class> baseClass = getAllClassByBase(context, BaseLocalDeviceModel.class);
        for (Class<? extends BaseLocalDeviceModel> clazz :
                baseClass) {
            ProductCodeAnnotation annotation = clazz.getAnnotation(ProductCodeAnnotation.class);
            for (int i = 0; i < annotation.value().length; i++) {
                if (annotation.value()[i] == productTag) {
                    try {
                        model = (BaseLocalDeviceModel) clazz.getDeclaredMethod("getInstance", new Class[0]).invoke(null, new Object[]{});
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return model;
                }
            }

        }
        return model;
    }

    /**
     * 给一个接口或者一个基类 找到当前目录下所有子类
     * 但是需要注意的是 为了节约反射好使 getClasses 直接取第一次获取的列表,
     * 如果想要的类型不是从新获取
     *
     * @param context
     * @param c
     * @return
     */
    public static List<Class> getAllClassByBase(Context context, Class c) {
        if (baseClass != null && baseClass.size() > 0) {
            if (c.isAssignableFrom(baseClass.get(0))) {
                return baseClass;
            } else {
                baseClass = null;
            }
        }
        baseClass = new ArrayList<Class>(); //返回结果
        //如果是一个接口，则不做处理
        String packageName = c.getPackage().getName(); //获得当前的包名
        try {
            List<String> allClass = getClasses(context, packageName); //获得当前包下以及子包下的所有类
            //判断是否是同一个类或接口
            for (int i = 0; i < allClass.size(); i++) {
                Class aClass = Class.forName(allClass.get(i));
                if (c.isAssignableFrom(aClass)) { //判断是不是一个类或接口
                    if (!c.equals(aClass)) { //本身不加进去
                        baseClass.add(aClass);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return baseClass;
    }

    //从一个包中查找出所有的类，在jar包中不能查找
    private static List<String> getClasses(Context context, String packageName) {
        List<String> classNameList = new ArrayList<String>();
        String path = context.getPackageCodePath();
        try {
            DexFile df = new DexFile(path);//通过DexFile查找当前的APK中可执行文件
            Enumeration<String> enumeration = df.entries();//获取df中的元素  这里包含了所有可执行的类名 该类名包含了包名+类名的方式
            while (enumeration.hasMoreElements()) {//遍历
                String className = enumeration.nextElement();
                if (className.contains(packageName)) {//在当前所有可执行的类里面查找包含有该包名的所有类
                    classNameList.add(className);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classNameList;

    }

    /**
     * 大部分的故障都是用bit位表示,但是类似于重庆蔡家这样的新风机 一个点位表示一个故障
     * 为了后台统一识别,我们吧这种 16个点位 组成一个值
     *
     * @param faults
     * @return
     */
    public static List<Integer> getFaults4FaultAdds(int[] faults) {
        if (faults == null) {
            return null;
        }
        List<Integer> faultAtts = new ArrayList<>();
        int faultNum = (int) Math.ceil((double) faults.length / (double) 16);
        for (int i = 0; i < faultNum; i++) {
            int[] adds = new int[16];
            System.arraycopy(faults, 16 * i, adds, 0, (faults.length - 16 * i) >= 16 ? 16 : faults.length - 16 * i);
            StringBuffer stringBuffer = new StringBuffer();
            for (Integer add :
                    adds) {
                stringBuffer.append(add > 0 ? 1 : 0);
            }
            faultAtts.add(Integer.valueOf(stringBuffer.toString(), 2));
        }
        return faultAtts;
    }

}
